﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;

namespace DARE
{
    public class CTerrainInfo
    {

        #region identifiers

        public class CInfo
        {
            public CInfo()
            {
                Initialize(Vector3.Zero, Vector3.Zero, Vector2.Zero);
            }

            public CInfo(Vector3 _position)
            {
                Initialize(_position, Vector3.Zero, Vector2.Zero);
            }

            public CInfo(Vector3 _position, Vector2 _texcoord)
            {
                Initialize(_position, Vector3.Zero, _texcoord);
            }

            private void Initialize(Vector3 _position, Vector3 _normal, Vector2 _texcoord)
            {
                position = _position;
                normal = _normal;
                texcoord = _texcoord;
            }

            public Vector3 position;
            public Vector3 normal;
            public Vector2 texcoord;
        }

        #endregion

        #region fields

        private Texture2D m_heightmap;
        private Vector3 m_pos;
        private CInfo[,] m_infos;
        private float m_bump;
        private float m_scale;
        private float m_width;
        private float m_height;
        private CNode m_ownerNode;

        #endregion

        #region properties

        public CNode OwnerNode
        {
            get { return m_ownerNode; }
            internal set { m_ownerNode = value; }
        }

        public Texture2D Heightmap
        {
            get { return m_heightmap; }
        }

        public float Width
        {
            get { return m_width; }
        }

        public float Height
        {
            get { return m_height; }
        }

        public float Bump
        {
            get { return m_bump; }
        }

        public float Scale
        {
            get { return m_scale; }
        }

        public CInfo[,] Infos
        {
            get { return m_infos; }
        }

        #endregion

        #region ctor

        public CTerrainInfo(Texture2D heightmap)
            : this(heightmap, 25, 1)
        {
        }

        public CTerrainInfo(Texture2D heightmap, float bump)
            : this(heightmap, bump, 1)
        {
        }

        public CTerrainInfo(Texture2D heightmap, float bump, float scale)
        {
            Initialize(heightmap, bump, scale);
        }

        private void Initialize(Texture2D heightmap, float bump, float scale)
        {
            if (heightmap == null)
                throw new Exception("CTerrainInfo: heightmap cannot be null !");
            m_heightmap = heightmap;
            m_bump = bump;
            m_scale = scale;
            m_width = m_heightmap.Width * m_scale;
            m_height = m_heightmap.Height * m_scale;
            m_pos.X = m_heightmap.Width / 2;
            m_pos.Z = m_heightmap.Height / 2;
            LoadData();
        }

        #endregion

        #region methods

        private void LoadData()
        {
            Color[] height = new Color[m_heightmap.Width * m_heightmap.Height];

            m_heightmap.GetData(height);
            m_infos = new CInfo[m_heightmap.Width, m_heightmap.Height];

            // vertex position / texture coordinate
            for (int z = 0; z < m_heightmap.Height; ++z)
                for (int x = 0; x < m_heightmap.Width; ++x)
                {
                    m_infos[x, z] = new CInfo();

                    m_infos[x, z].position = new Vector3(
                        (x - m_pos.X + 0.5f) * m_scale, 
                        (height[x + z * m_heightmap.Width].R / 255.0f) * m_bump, 
                        (z - m_pos.Z + 0.5f) * m_scale);

                    m_infos[x, z].texcoord = new Vector2(
                        (float)x / m_heightmap.Width, 
                        (float)z / m_heightmap.Width);
                }

            // normal
            for (int z = 0; z < m_heightmap.Height - 1; ++z)
                for (int x = 0; x < m_heightmap.Width - 1; ++x)
                {
                    Vector3 pos = m_infos[x, z].position;
                    Vector3 right = m_infos[x + 1, z].position;
                    Vector3 down = m_infos[x, z + 1].position;

                    Vector3 horizontal = right - pos;
                    Vector3 vertical = down - pos;

                    Vector3 normal = Vector3.Cross(vertical, horizontal);

                    m_infos[x, z].normal += normal;
                    m_infos[x + 1, z].normal += normal;
                    m_infos[x, z + 1].normal += normal;
                }
            for (int z = 0; z < m_heightmap.Height; ++z)
                for (int x = 0; x < m_heightmap.Width; ++x)
                    m_infos[x, z].normal.Normalize();
        }

        public CInfo[,] GetInfos(int xMin, int xMax, int zMin, int zMax)
        {
            CInfo[,] infos = new CInfo[xMax - xMin, zMax - zMin];

            for (int z = zMin; z < zMax; ++z)
                for (int x = xMin; x < xMax; ++x)
                    infos[x - xMin, z - zMin] = m_infos[x, z];
            return infos;
        }

        public bool IsOnTerrain(Vector3 position)
        {
            Vector3 pos = position - m_pos;

            return ((pos.X > 0) && (pos.X < Width) && (pos.Z > 0) && (pos.Z < Height));
        }

        public float GetHeight(Vector3 position)
        {
            return GetHeight(position.X, position.Z);
        }

        public float GetHeight(float x, float z)
        {
            x -= m_pos.X;
            z -= m_pos.Z;

            int left = (int)x / (int)m_scale;
            int top = (int)z / (int)m_scale;

            float xNorm = (x % m_scale) / m_scale;
            float zNorm = (z % m_scale) / m_scale;

            float topHeight = MathHelper.Lerp(m_infos[left, top].position.Y, m_infos[left + 1, top].position.Y, xNorm);
            float bottomHeight = MathHelper.Lerp(m_infos[left, top + 1].position.Y, m_infos[left + 1, top + 1].position.Y, xNorm);

            return MathHelper.Lerp(topHeight, bottomHeight, zNorm);
        }

        #endregion

    }
}
